/*-------------------------------------------------------------------------
 *
 * pg_prewarm.c
 *		  预热工具
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/pg_prewarm/pg_prewarm.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/stat.h>
#include <unistd.h>

#include "access/relation.h"
#include "fmgr.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(pg_prewarm);

typedef enum
{
	PREWARM_PREFETCH,
	PREWARM_READ,
	PREWARM_BUFFER
} PrewarmType;

static PGAlignedBlock blockbuffer;

/*
 * pg_prewarm(regclass, mode text, fork text,
 *			  first_block int8, last_block int8)
 *
 * 第一个参数是要预热的关系；第二个控制预热的方式；合法选项为'prefetch'、'read'和'buffer'。
 * 第三个是要预热的关系分叉的名称。第四和第五个参数指定要预热的第一个和最后一个块。
 * 如果第四个参数为NULL，则将其视为0；如果第五个参数为NULL，则将其视为关系中的块数。
 * 返回值是成功预热的块数。
 */
Datum pg_prewarm(PG_FUNCTION_ARGS)
{
	Oid			fc_relOid;
	text	   *fc_forkName;
	text	   *fc_type;
	int64		fc_first_block;
	int64		fc_last_block;
	int64		fc_nblocks;
	int64		fc_blocks_done = 0;
	int64		fc_block;
	Relation	fc_rel;
	ForkNumber	fc_forkNumber;
	char	   *fc_forkString;
	char	   *fc_ttype;
	PrewarmType fc_ptype;
	AclResult	fc_aclresult;

	/* 基本的合理性检查。 */
	if (PG_ARGISNULL(0))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("relation cannot be null")));
	fc_relOid = PG_GETARG_OID(0);
	if (PG_ARGISNULL(1))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("prewarm type cannot be null")));
	fc_type = PG_GETARG_TEXT_PP(1);
	fc_ttype = text_to_cstring(fc_type);
	if (strcmp(fc_ttype, "prefetch") == 0)
		fc_ptype = PREWARM_PREFETCH;
	else if (strcmp(fc_ttype, "read") == 0)
		fc_ptype = PREWARM_READ;
	else if (strcmp(fc_ttype, "buffer") == 0)
		fc_ptype = PREWARM_BUFFER;
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid prewarm type"),
				 errhint("Valid prewarm types are \"prefetch\", \"read\", and \"buffer\".")));
		PG_RETURN_INT64(0);		/* 安抚编译器。 */
	}
	if (PG_ARGISNULL(2))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("relation fork cannot be null")));
	fc_forkName = PG_GETARG_TEXT_PP(2);
	fc_forkString = text_to_cstring(fc_forkName);
	fc_forkNumber = forkname_to_number(fc_forkString);

	/* 打开关系并检查权限。 */
	fc_rel = relation_open(fc_relOid, AccessShareLock);
	fc_aclresult = pg_class_aclcheck(fc_relOid, GetUserId(), ACL_SELECT);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, get_relkind_objtype(fc_rel->rd_rel->relkind), get_rel_name(fc_relOid));

	/* 检查分叉是否存在。 */
	if (!smgrexists(RelationGetSmgr(fc_rel), fc_forkNumber))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("fork \"%s\" does not exist for this relation",
						fc_forkString)));

	/* 验证区块编号，或处理NULL。 */
	fc_nblocks = RelationGetNumberOfBlocksInFork(fc_rel, fc_forkNumber);
	if (PG_ARGISNULL(3))
		fc_first_block = 0;
	else
	{
		fc_first_block = PG_GETARG_INT64(3);
		if (fc_first_block < 0 || fc_first_block >= fc_nblocks)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("starting block number must be between 0 and %lld",
							(long long) (fc_nblocks - 1))));
	}
	if (PG_ARGISNULL(4))
		fc_last_block = fc_nblocks - 1;
	else
	{
		fc_last_block = PG_GETARG_INT64(4);
		if (fc_last_block < 0 || fc_last_block >= fc_nblocks)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ending block number must be between 0 and %lld",
							(long long) (fc_nblocks - 1))));
	}

	/* 现在我们准备好进行实际工作。 */
	if (fc_ptype == PREWARM_PREFETCH)
	{
#ifdef USE_PREFETCH

		/*
		 * 在预取模式中，我们只是提示操作系统去读取块，但我们
		 * 不知道它是否真的这样做，也不等待它完成。
		 *
		 * 可能更好的做法是以每次一兆字节或甚至整段的方式传递我们的预取请求，
		 * 但目前没有实际的方法做到这一点而不严重违反模块化原则，所以我们就这样做。
		 */
		for (fc_block = fc_first_block; fc_block <= fc_last_block; ++fc_block)
		{
			CHECK_FOR_INTERRUPTS();
			PrefetchBuffer(fc_rel, fc_forkNumber, fc_block);
			++fc_blocks_done;
		}
#else
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("prefetch is not supported by this build")));
#endif
	}
	else if (fc_ptype == PREWARM_READ)
	{
		/*
		 * 在读取模式下，我们实际上读取块，但不进入共享缓冲区。
		 * 这比预取模式更具可移植性（它在任何地方都有效），并且是同步的。
		 */
		for (fc_block = fc_first_block; fc_block <= fc_last_block; ++fc_block)
		{
			CHECK_FOR_INTERRUPTS();
			smgrread(RelationGetSmgr(fc_rel), fc_forkNumber, fc_block, blockbuffer.data);
			++fc_blocks_done;
		}
	}
	else if (fc_ptype == PREWARM_BUFFER)
	{
		/*
		 * 在缓冲模式下，我们实际上将数据拉入共享缓冲区。
		 */
		for (fc_block = fc_first_block; fc_block <= fc_last_block; ++fc_block)
		{
			Buffer		fc_buf;

			CHECK_FOR_INTERRUPTS();
			fc_buf = ReadBufferExtended(fc_rel, fc_forkNumber, fc_block, RBM_NORMAL, NULL);
			ReleaseBuffer(fc_buf);
			++fc_blocks_done;
		}
	}

	/* 关闭关系，释放锁定。 */
	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_INT64(fc_blocks_done);
}
